Skip to content

Preserve staged counterpart display names in Raven identity repairs#471

Draft
Copilot wants to merge 4 commits intomainfrom
copilot/fix-staging-identity-bug
Draft

Preserve staged counterpart display names in Raven identity repairs#471
Copilot wants to merge 4 commits intomainfrom
copilot/fix-staging-identity-bug

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 30, 2026

Raven could retain an active staged counterpart slot while losing the counterpart’s usable name, producing contradictory replies like “Stephie is not staged” followed by “the active staged counterpart is the staged counterpart.” The staged counterpart slot should only render with a canonical display name, or fail into a repairable state.

  • Counterpart identity preservation

    • Carries displayName and canonicalName through client payloads, request sanitization, and server-side Profile typing.
    • Resolves staged counterpart labels from canonicalName → displayName → name.
  • Generic placeholder rejection

    • Adds shared counterpart identity utilities to reject labels such as:
      • the staged counterpart
      • active staged counterpart
      • Profile 3
      • unknown
    • Prevents unnamed/generic observer profiles from becoming the active counterpart.
  • Repair/correction behavior

    • If the challenged name matches the active staged counterpart, Raven now emits a correction instead of a denial.
    • If a counterpart slot exists without a usable name, Raven returns a state repair message instead of substituting a placeholder.
const activeCounterpartName = isUsableCounterpartDisplayName(input.activeCounterpartName)
  ? input.activeCounterpartName.trim()
  : null;

if (challengedName && normalizeName(challengedName) === normalizeName(activeCounterpartName)) {
  return `Correction: ${activeCounterpartName} is the active staged counterpart in this session. I should keep the field anchored to ${anchorName} and ${activeCounterpartName}.`;
}
  • Regression coverage
    • Covers DH Cross + Stephie anchoring.
    • Asserts Raven does not emit:
      • Stephie is not a staged profile
      • the staged counterpart as primary identity
      • active staged counterpart here is the staged counterpart

Copilot AI and others added 4 commits April 30, 2026 21:21
Agent-Logs-Url: https://github.com/DHCross/Shipyard/sessions/bd1e4e3a-b5f9-4f5d-a4a8-681bc34c2943

Co-authored-by: DHCross <45954119+DHCross@users.noreply.github.com>
Agent-Logs-Url: https://github.com/DHCross/Shipyard/sessions/bd1e4e3a-b5f9-4f5d-a4a8-681bc34c2943

Co-authored-by: DHCross <45954119+DHCross@users.noreply.github.com>
Agent-Logs-Url: https://github.com/DHCross/Shipyard/sessions/bd1e4e3a-b5f9-4f5d-a4a8-681bc34c2943

Co-authored-by: DHCross <45954119+DHCross@users.noreply.github.com>
Agent-Logs-Url: https://github.com/DHCross/Shipyard/sessions/bd1e4e3a-b5f9-4f5d-a4a8-681bc34c2943

Co-authored-by: DHCross <45954119+DHCross@users.noreply.github.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 30, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
shipyard Ready Ready Preview, Comment Apr 30, 2026 10:05pm

Request Review

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens Raven’s staged counterpart identity handling so “active staged counterpart” slots only render when a usable canonical display name is available, and identity-repair flows avoid generic placeholder substitutions.

Changes:

  • Added shared counterpart identity utilities to classify/reject generic placeholder labels and to validate usable counterpart display names.
  • Updated staged profile snapshotting to resolve names from canonicalName → displayName → name, exclude unnamed/generic observer slots, and propagate richer name fields through the API payload/type surface.
  • Updated provenance repair + entity guard behavior to emit corrections when challenged names match the active staged counterpart, and to return a repair message when a staged slot exists without a usable name (with regression tests).

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
vessel/src/lib/raven/stagedProfileSnapshot.ts Resolve counterpart display name from canonical/display/name and drop unnamed/generic observer slots.
vessel/src/lib/raven/counterpartIdentity.ts New helper functions/pattern for rejecting generic counterpart labels.
vessel/src/lib/raven/tests/stagedProfileSnapshot.test.ts Regression tests for displayName recovery and unnamed counterpart exclusion.
vessel/src/hooks/useOracleChat.ts Include displayName/canonicalName in client payloads.
vessel/src/app/api/raven-chat/types.ts Extend Profile type with displayName/canonicalName.
vessel/src/app/api/raven-chat/route.ts Thread “staged counterpart slot present” signal into repair + entity guard.
vessel/src/app/api/raven-chat/requestParsing.ts Allowlist displayName/canonicalName in profile sanitization.
vessel/src/app/api/raven-chat/entityGuard.ts Pass staged-slot-present signal through to provenance repair reply generation.
vessel/src/app/api/raven-chat/counterpartProvenance.ts Use usable-name validation; add correction + “slot present but unnamed” repair messaging.
vessel/src/app/api/raven-chat/tests/requestParsing.test.ts Test that sanitizeProfile preserves displayName/canonicalName.
vessel/src/app/api/raven-chat/tests/counterpartProvenance.test.ts Tests for correction behavior and placeholder-rejection repair replies.
vessel/sherlog-velocity/data/session-log.jsonl Sherlog telemetry log update.
vessel/sherlog-velocity/data/self-model.json Sherlog model artifact update.
vessel/sherlog-velocity/data/gap-history.jsonl Sherlog gap history update.
vessel/sherlog-velocity/data/active-session.json Remove active session artifact.

Comment on lines 480 to +484
return {
profileId: asTrimmedText(profile.profileId, 160) || asTrimmedText(profile.id, 160),
name: asTrimmedText(profile.name, 120),
displayName: asTrimmedText(profile.displayName, 120),
canonicalName: asTrimmedText(profile.canonicalName, 120),
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sanitizeProfile() now allowlists displayName/canonicalName, but mergeHydratedProfile() (used after vault hydration) does not merge these fields. This means the new identity fields can be dropped whenever a staged profile is hydrated/merged, defeating the preservation/repair flow. Consider extending mergeHydratedProfile() to carry displayName/canonicalName through the same way name is handled (prefer explicit profile values, otherwise fallback).

Copilot uses AI. Check for mistakes.

const orderedProfiles = rawOrdered.filter((entry) => {
if (!entry.displayName) {
excludedNames.push(entry.profile.profileId || 'unnamed staged counterpart');
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

excludedNames is documented as a list of excluded names, but this new branch pushes profileId when displayName is empty. That changes the meaning/shape of excludedNames (IDs vs names) and could break callers that log/surface it as a user-facing backstage notice. Either push a consistently name-like label here (e.g. a stable placeholder) or update the type/docs (and any consumers) to explicitly allow IDs in excludedNames.

Suggested change
excludedNames.push(entry.profile.profileId || 'unnamed staged counterpart');
excludedNames.push('unnamed staged counterpart');

Copilot uses AI. Check for mistakes.
Comment on lines +668 to 671
(candidate) => String(candidate.role || '').trim().toUpperCase() !== 'SELF',
) || Boolean(stagedProfileSnapshot.activeCounterpart);
const polyadicCounterpartProfiles = stagedProfiles.filter(
(candidate) => String(candidate.role || '').trim().toUpperCase() !== 'SELF',
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stagedCounterpartSlotPresent uses role !== 'SELF' to detect a counterpart slot, but sanitizeProfile() normalizes role to 'SELF' | 'OBSERVER' | ''. With the current check, an invalid/empty role ('') will be treated as a staged counterpart slot, which can incorrectly trigger the “slot present but name missing” repair path. Consider checking explicitly for 'OBSERVER' (and similarly wherever counterpart presence is inferred) so invalid roles don’t masquerade as a counterpart slot.

Suggested change
(candidate) => String(candidate.role || '').trim().toUpperCase() !== 'SELF',
) || Boolean(stagedProfileSnapshot.activeCounterpart);
const polyadicCounterpartProfiles = stagedProfiles.filter(
(candidate) => String(candidate.role || '').trim().toUpperCase() !== 'SELF',
(candidate) => String(candidate.role || '').trim().toUpperCase() === 'OBSERVER',
) || Boolean(stagedProfileSnapshot.activeCounterpart);
const polyadicCounterpartProfiles = stagedProfiles.filter(
(candidate) => String(candidate.role || '').trim().toUpperCase() === 'OBSERVER',

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants